package com.idanchuang.component.mybatis.plus.plugins;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.parser.SqlInfo;
import com.idanchuang.component.mybatis.plus.config.MybatisConfigBean;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.mapping.StatementType;
import org.apache.ibatis.reflection.MetaObject;

import java.util.Map;

/**
 * @author yjy
 * Created at 2021/4/6 9:52
 **/
public class QueryLimitSqlParser implements AvailableSqlParser {

    private final MybatisConfigBean mybatisConfigBean;

    public QueryLimitSqlParser(MybatisConfigBean mybatisConfigBean) {
        this.mybatisConfigBean = mybatisConfigBean;
    }

    @Override
    public SqlInfo parser(MetaObject metaObject, String sql) {
        if (mybatisConfigBean == null || !mybatisConfigBean.isEnableLimit()) {
            return null;
        }
        if (mybatisConfigBean.getMaxLimit() <= 0) {
            return null;
        }
        // 跳过 非select 与 存储过程
        MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
        if (SqlCommandType.SELECT != mappedStatement.getSqlCommandType()
                || StatementType.CALLABLE == mappedStatement.getStatementType()) {
            return null;
        }
        // 如果有page参数, 则不作处理
        if (hasPage(metaObject)) {
            return null;
        }
        // 处理查询限制
        sql = handleLimitSql(sql, mybatisConfigBean.getMaxLimit());
        return SqlInfo.newInstance().setSql(sql);
    }

//    public static void main(String[] args) {
//        String[] sqls = {
//                "SELECT * from aaa",
//                "SELECT a.id id, a.bb_id as bbId,a.real_name realName,b.age age from aaa a left join bbb b on a.bb_id = b.id \n" +
//                        "\t\twhere a.status = 1 and b.age between 10 and 99 order by age desc\n",
//                "SELECT a.id id, a.bb_id as bbId,a.real_name realName,b.age age from aaa a left join bbb b on a.bb_id = b.id \n" +
//                        "\t\twhere a.status = 1 and b.age between 10 and 99 order by age desc\n for update",
//                "SELECT a.id id, a.bb_id as bbId,a.real_name realName,b.age age from aaa a left join bbb b on a.bb_id = b.id \n" +
//                        "\t\twhere a.status = 1 and b.age between 10 and 99 order by age desc\n for update ",
//                "SELECT a.id id, a.bb_id as bbId,a.real_name realName,b.age age from aaa a left join bbb b on a.bb_id = b.id \n" +
//                        "\t\twhere a.status = 1 and b.age between 10 and 99 order by age desc\n for update;"
//        } ;
//        for (String sql : sqls) {
//            System.out.println(handleLimitSql(sql, 10000));
//
//        }
//    }

    /**
     * 处理sql查询限制
     * @param sql 原始sql
     * @param maxLimit 最大查询限制
     * @return 处理后的sql
     */
    private static String handleLimitSql(String sql, int maxLimit) {
        sql = sql.trim();
        boolean hasEndToken = sql.endsWith(";");
        if (hasEndToken) {
            sql = sql.substring(0, sql.length() - 1);
            sql = sql.trim();
        }

        String lastInsertIdFunc = "last_insert_id()";
        if (endsWithIgnoreCase(sql, lastInsertIdFunc)) {
            return sql;
        }
        sql = sql.replace("\n", " ").replace("\t", " ");
        if (!containsIgnoreCase(sql, " limit ")) {
            boolean forUpdate = false;
            if (endsWithIgnoreCase(sql, " update")) {
                String tSql = sql;
                tSql = tSql.substring(0, tSql.length() - 6).trim();
                if (endsWithIgnoreCase(tSql, " for")) {
                    forUpdate = true;
                    sql = tSql.substring(0, tSql.length() - 3).trim();
                }
            }
            sql += " limit " + maxLimit;
            if (forUpdate) {
                sql += " for update";
            }
        }
        if (hasEndToken) {
            sql += ";";
        }
        return sql;
    }

    /**
     * 判断是否以某个字符串结尾 (大小写不敏感)
     * @param originString 原始字符串
     * @param endsWith 以..结尾
     * @return 是否匹配
     */
    private static boolean endsWithIgnoreCase(String originString, String endsWith) {
        if (originString == null || originString.length() < endsWith.length()) {
            return false;
        }
        String lastLengthString = originString.substring(originString.length() - endsWith.length());
        return endsWith.equalsIgnoreCase(lastLengthString);
    }

    /**
     * 判断是否包含某个字符串 (大小写不敏感)
     * @param originString 原始字符串
     * @param contains 以..结尾
     * @return 是否匹配
     */
    private static boolean containsIgnoreCase(String originString, String contains) {
        if (originString == null || originString.length() < contains.length()) {
            return false;
        }
        return originString.toLowerCase().contains(contains.toLowerCase());
    }

    /**
     * 是否有分页参数
     * @param metaObject ..
     * @return 是否
     */
    private boolean hasPage(MetaObject metaObject) {
        // 针对定义了rowBounds，做为mapper接口方法的参数
        BoundSql boundSql = (BoundSql) metaObject.getValue("delegate.boundSql");
        Object paramObj = boundSql.getParameterObject();

        // 判断参数里是否有page对象
        IPage<?> page = null;
        if (paramObj instanceof IPage) {
            page = (IPage<?>) paramObj;
        } else if (paramObj instanceof Map) {
            for (Object arg : ((Map<?, ?>) paramObj).values()) {
                if (arg instanceof IPage) {
                    page = (IPage<?>) arg;
                    break;
                }
            }
        }
        return page != null;
    }
}